// wrapped by build app
define("dojox/widget/RollingList", ["dijit", "dojo", "dojox", "dojo/i18n!dijit/nls/common", "dojo/require!dojo/window,dijit/layout/ContentPane,dijit/_Templated,dijit/_Contained,dijit/layout/_LayoutWidget,dijit/Menu,dijit/form/Button,dijit/focus,dijit/_base/focus,dojox/html/metrics,dojo/i18n"], function (dijit, dojo, dojox) {
    dojo.provide("dojox.widget.RollingList");
    dojo.experimental("dojox.widget.RollingList");

    dojo.require("dojo.window");

    dojo.require("dijit.layout.ContentPane");
    dojo.require("dijit._Templated");
    dojo.require("dijit._Contained");
    dojo.require("dijit.layout._LayoutWidget");
    dojo.require("dijit.Menu");
    dojo.require("dijit.form.Button");
    dojo.require("dijit.focus");		// dijit.focus()
    dojo.require("dijit._base.focus");	// dijit.getFocus()

    dojo.require("dojox.html.metrics");

    dojo.require("dojo.i18n");
    dojo.requireLocalization("dijit", "common");

    dojo.declare("dojox.widget._RollingListPane",
        [dijit.layout.ContentPane, dijit._Templated, dijit._Contained], {
            // summary:
            //		a core pane that can be attached to a RollingList.  All panes
            //		should extend this one

            // templateString: string
            //		our template
            templateString:'<div class="dojoxRollingListPane"><table><tbody><tr><td dojoAttachPoint="containerNode"></td></tr></tbody></div>',

            // parentWidget: dojox.widget.RollingList
            //		Our rolling list widget
            parentWidget:null,

            // parentPane: dojox.widget._RollingListPane
            //		The pane that immediately precedes ours
            parentPane:null,

            // store: store
            //		the store we must use
            store:null,

            // items: item[]
            //		an array of (possibly not-yet-loaded) items to display in this.
            //		If this array is null, then the query and query options are used to
            //		get the top-level items to use.  This array is also used to watch and
            //		see if the pane needs to be reloaded (store notifications are handled)
            //		by the pane
            items:null,

            // query: object
            //		a query to pass to the datastore.  This is only used if items are null
            query:null,

            // queryOptions: object
            //		query options to be passed to the datastore
            queryOptions:null,

            // focusByNode: boolean
            //		set to false if the subclass will handle its own node focusing
            _focusByNode:true,

            // minWidth: integer
            //		the width (in px) for this pane
            minWidth:0,

            _setContentAndScroll:function (/*String|DomNode|Nodelist*/cont, /*Boolean?*/isFakeContent) {
                // summary:
                //		sets the value of the content and scrolls it into view
                this._setContent(cont, isFakeContent);
                this.parentWidget.scrollIntoView(this);
            },

            _updateNodeWidth:function (n, min) {
                // summary:
                //		updates the min width of the pane to be minPaneWidth
                n.style.width = "";
                var nWidth = dojo.marginBox(n).w;
                if (nWidth < min) {
                    dojo.marginBox(n, {w:min});
                }
            },

            _onMinWidthChange:function (v) {
                // Called when the min width of a pane has changed
                this._updateNodeWidth(this.domNode, v);
            },

            _setMinWidthAttr:function (v) {
                if (v !== this.minWidth) {
                    this.minWidth = v;
                    this._onMinWidthChange(v);
                }
            },

            startup:function () {
                if (this._started) {
                    return;
                }
                if (this.store && this.store.getFeatures()["dojo.data.api.Notification"]) {
                    window.setTimeout(dojo.hitch(this, function () {
                        // Set connections after a slight timeout to avoid getting in the
                        // condition where we are setting them while events are still
                        // being fired
                        this.connect(this.store, "onSet", "_onSetItem");
                        this.connect(this.store, "onNew", "_onNewItem");
                        this.connect(this.store, "onDelete", "_onDeleteItem");
                    }), 1);
                }
                this.connect(this.focusNode || this.domNode, "onkeypress", "_focusKey");
                this.parentWidget._updateClass(this.domNode, "Pane");
                this.inherited(arguments);
                this._onMinWidthChange(this.minWidth);
            },

            _focusKey:function (/*Event*/e) {
                // summary:
                //		called when a keypress happens on the widget
                if (e.charOrCode == dojo.keys.BACKSPACE) {
                    dojo.stopEvent(e);
                    return;
                } else if (e.charOrCode == dojo.keys.LEFT_ARROW && this.parentPane) {
                    this.parentPane.focus();
                    this.parentWidget.scrollIntoView(this.parentPane);
                } else if (e.charOrCode == dojo.keys.ENTER) {
                    this.parentWidget._onExecute();
                }
            },

            focus:function (/*boolean*/force) {
                // summary:
                //		sets the focus to this current widget
                if (this.parentWidget._focusedPane != this) {
                    this.parentWidget._focusedPane = this;
                    this.parentWidget.scrollIntoView(this);
                    if (this._focusByNode && (!this.parentWidget._savedFocus || force)) {
                        try {
                            (this.focusNode || this.domNode).focus();
                        } catch (e) {
                        }
                    }
                }
            },

            _onShow:function () {
                // summary:
                //		checks that the store is loaded
                this.inherited(arguments);
                if ((this.store || this.items) && ((this.refreshOnShow && this.domNode) || (!this.isLoaded && this.domNode))) {
                    this.refresh();
                }
            },

            _load:function () {
                // summary:
                //		sets the "loading" message and then kicks off a query asyncronously
                this.isLoaded = false;
                if (this.items) {
                    this._setContentAndScroll(this.onLoadStart(), true);
                    window.setTimeout(dojo.hitch(this, "_doQuery"), 1);
                } else {
                    this._doQuery();
                }
            },

            _doLoadItems:function (/*item[]*/items, /*function*/callback) {
                // summary:
                //		loads the given items, and then calls the callback when they
                //		are finished.
                var _waitCount = 0, store = this.store;
                dojo.forEach(items, function (item) {
                    if (!store.isItemLoaded(item)) {
                        _waitCount++;
                    }
                });
                if (_waitCount === 0) {
                    callback();
                } else {
                    var onItem = function (item) {
                        _waitCount--;
                        if ((_waitCount) === 0) {
                            callback();
                        }
                    };
                    dojo.forEach(items, function (item) {
                        if (!store.isItemLoaded(item)) {
                            store.loadItem({item:item, onItem:onItem});
                        }
                    });
                }
            },

            _doQuery:function () {
                // summary:
                //		either runs the query or loads potentially not-yet-loaded items.
                if (!this.domNode) {
                    return;
                }
                var preload = this.parentWidget.preloadItems;
                preload = (preload === true || (this.items && this.items.length <= Number(preload)));
                if (this.items && preload) {
                    this._doLoadItems(this.items, dojo.hitch(this, "onItems"));
                } else if (this.items) {
                    this.onItems();
                } else {
                    this._setContentAndScroll(this.onFetchStart(), true);
                    this.store.fetch({query:this.query,
                        onComplete:function (items) {
                            this.items = items;
                            this.onItems();
                        },
                        onError:function (e) {
                            this._onError("Fetch", e);
                        },
                        scope:this});
                }
            },

            _hasItem:function (/* item */ item) {
                // summary:
                //		returns whether or not the given item is handled by this
                //		pane
                var items = this.items || [];
                for (var i = 0, myItem; (myItem = items[i]); i++) {
                    if (this.parentWidget._itemsMatch(myItem, item)) {
                        return true;
                    }
                }
                return false;
            },

            _onSetItem:function (/* item */ item, /* attribute-name-string */ attribute, /* Object|Array */ oldValue, /* Object|Array */ newValue) {
                // summary:
                //		called when an item in the store has changed
                if (this._hasItem(item)) {
                    this.refresh();
                }
            },

            _onNewItem:function (/* item */ newItem, /*object?*/ parentInfo) {
                // summary:
                //		called when an item is added to the store
                var sel;
                if ((!parentInfo && !this.parentPane) ||
                    (parentInfo && this.parentPane && this.parentPane._hasItem(parentInfo.item) &&
                        (sel = this.parentPane._getSelected()) && this.parentWidget._itemsMatch(sel.item, parentInfo.item))) {
                    this.items.push(newItem);
                    this.refresh();
                } else if (parentInfo && this.parentPane && this._hasItem(parentInfo.item)) {
                    this.refresh();
                }
            },

            _onDeleteItem:function (/* item */ deletedItem) {
                // summary:
                //		called when an item is removed from the store
                if (this._hasItem(deletedItem)) {
                    this.items = dojo.filter(this.items, function (i) {
                        return (i != deletedItem);
                    });
                    this.refresh();
                }
            },

            onFetchStart:function () {
                // summary:
                //		called before a fetch starts
                return this.loadingMessage;
            },

            onFetchError:function (/*Error*/ error) {
                // summary:
                //		called when a fetch error occurs.
                return this.errorMessage;
            },

            onLoadStart:function () {
                // summary:
                //		called before a load starts
                return this.loadingMessage;
            },

            onLoadError:function (/*Error*/ error) {
                // summary:
                //		called when a load error occurs.
                return this.errorMessage;
            },

            onItems:function () {
                // summary:
                //		called after a fetch or load - at this point, this.items should be
                //  	set and loaded.  Override this function to "do your stuff"
                if (!this.onLoadDeferred) {
                    this.cancel();
                    this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel"));
                }
                this._onLoadHandler();
            }

        });

    dojo.declare("dojox.widget._RollingListGroupPane",
        [dojox.widget._RollingListPane], {
            // summary:
            //		a pane that will handle groups (treats them as menu items)

            // templateString: string
            //		our template
            templateString:'<div><div dojoAttachPoint="containerNode"></div>' +
                '<div dojoAttachPoint="menuContainer">' +
                '<div dojoAttachPoint="menuNode"></div>' +
                '</div></div>',

            // _menu: dijit.Menu
            //		The menu that we will call addChild() on for adding items
            _menu:null,

            _setContent:function (/*String|DomNode|Nodelist*/cont) {
                if (!this._menu) {
                    // Only set the content if we don't already have a menu
                    this.inherited(arguments);
                }
            },

            _onMinWidthChange:function (v) {
                // override and resize the menu instead
                if (!this._menu) {
                    return;
                }
                var dWidth = dojo.marginBox(this.domNode).w;
                var mWidth = dojo.marginBox(this._menu.domNode).w;
                this._updateNodeWidth(this._menu.domNode, v - (dWidth - mWidth));
            },

            onItems:function () {
                // summary:
                //		called after a fetch or load
                var selectItem, hadChildren = false;
                if (this._menu) {
                    selectItem = this._getSelected();
                    this._menu.destroyRecursive();
                }
                this._menu = this._getMenu();
                var child, selectMenuItem;
                if (this.items.length) {
                    dojo.forEach(this.items, function (item) {
                        child = this.parentWidget._getMenuItemForItem(item, this);
                        if (child) {
                            if (selectItem && this.parentWidget._itemsMatch(child.item, selectItem.item)) {
                                selectMenuItem = child;
                            }
                            this._menu.addChild(child);
                        }
                    }, this);
                } else {
                    child = this.parentWidget._getMenuItemForItem(null, this);
                    if (child) {
                        this._menu.addChild(child);
                    }
                }
                if (selectMenuItem) {
                    this._setSelected(selectMenuItem);
                    if ((selectItem && !selectItem.children && selectMenuItem.children) ||
                        (selectItem && selectItem.children && !selectMenuItem.children)) {
                        var itemPane = this.parentWidget._getPaneForItem(selectMenuItem.item, this, selectMenuItem.children);
                        if (itemPane) {
                            this.parentWidget.addChild(itemPane, this.getIndexInParent() + 1);
                        } else {
                            this.parentWidget._removeAfter(this);
                            this.parentWidget._onItemClick(null, this, selectMenuItem.item, selectMenuItem.children);
                        }
                    }
                } else if (selectItem) {
                    this.parentWidget._removeAfter(this);
                }
                this.containerNode.innerHTML = "";
                this.containerNode.appendChild(this._menu.domNode);
                this.parentWidget.scrollIntoView(this);
                this._checkScrollConnection(true);
                this.inherited(arguments);
                this._onMinWidthChange(this.minWidth);
            },

            _checkScrollConnection:function (doLoad) {
                // summary:
                //		checks whether or not we need to connect to our onscroll
                //		function
                var store = this.store;
                if (this._scrollConn) {
                    this.disconnect(this._scrollConn);
                }
                delete this._scrollConn;
                if (!dojo.every(this.items, function (i) {
                    return store.isItemLoaded(i);
                })) {
                    if (doLoad) {
                        this._loadVisibleItems();
                    }
                    this._scrollConn = this.connect(this.domNode, "onscroll", "_onScrollPane");
                }
            },

            startup:function () {
                this.inherited(arguments);
                this.parentWidget._updateClass(this.domNode, "GroupPane");
            },

            focus:function (/*boolean*/force) {
                // summary:
                //		sets the focus to this current widget
                if (this._menu) {
                    if (this._pendingFocus) {
                        this.disconnect(this._pendingFocus);
                    }
                    delete this._pendingFocus;

                    // We focus the right widget - either the focusedChild, the
                    // selected node, the first menu item, or the menu itself
                    var focusWidget = this._menu.focusedChild;
                    if (!focusWidget) {
                        var focusNode = dojo.query(".dojoxRollingListItemSelected", this.domNode)[0];
                        if (focusNode) {
                            focusWidget = dijit.byNode(focusNode);
                        }
                    }
                    if (!focusWidget) {
                        focusWidget = this._menu.getChildren()[0] || this._menu;
                    }
                    this._focusByNode = false;
                    if (focusWidget.focusNode) {
                        if (!this.parentWidget._savedFocus || force) {
                            try {
                                focusWidget.focusNode.focus();
                            } catch (e) {
                            }
                        }
                        window.setTimeout(function () {
                            try {
                                dojo.window.scrollIntoView(focusWidget.focusNode);
                            } catch (e) {
                            }
                        }, 1);
                    } else if (focusWidget.focus) {
                        if (!this.parentWidget._savedFocus || force) {
                            focusWidget.focus();
                        }
                    } else {
                        this._focusByNode = true;
                    }
                    this.inherited(arguments);
                } else if (!this._pendingFocus) {
                    this._pendingFocus = this.connect(this, "onItems", "focus");
                }
            },

            _getMenu:function () {
                // summary:
                //		returns a widget to be used for the container widget.
                var self = this;
                var menu = new dijit.Menu({
                    parentMenu:this.parentPane ? this.parentPane._menu : null,
                    onCancel:function (/*Boolean*/ closeAll) {
                        if (self.parentPane) {
                            self.parentPane.focus(true);
                        }
                    },
                    _moveToPopup:function (/*Event*/ evt) {
                        if (this.focusedChild && !this.focusedChild.disabled) {
                            this.onItemClick(this.focusedChild, evt);
                        }
                    }
                }, this.menuNode);
                this.connect(menu, "onItemClick", function (/*dijit.MenuItem*/ item, /*Event*/ evt) {
                    if (item.disabled) {
                        return;
                    }
                    evt.alreadySelected = dojo.hasClass(item.domNode, "dojoxRollingListItemSelected");
                    if (evt.alreadySelected &&
                        ((evt.type == "keypress" && evt.charOrCode != dojo.keys.ENTER) ||
                            (evt.type == "internal"))) {
                        var p = this.parentWidget.getChildren()[this.getIndexInParent() + 1];
                        if (p) {
                            p.focus(true);
                            this.parentWidget.scrollIntoView(p);
                        }
                    } else {
                        this._setSelected(item, menu);
                        this.parentWidget._onItemClick(evt, this, item.item, item.children);
                        if (evt.type == "keypress" && evt.charOrCode == dojo.keys.ENTER) {
                            this.parentWidget._onExecute();
                        }
                    }
                });
                if (!menu._started) {
                    menu.startup();
                }
                return menu;
            },

            _onScrollPane:function () {
                // summary:
                //		called when the pane has been scrolled - it sets a timeout
                //		so that we don't try and load our visible items too often during
                //		a scroll
                if (this._visibleLoadPending) {
                    window.clearTimeout(this._visibleLoadPending);
                }
                this._visibleLoadPending = window.setTimeout(dojo.hitch(this, "_loadVisibleItems"), 500);
            },

            _loadVisibleItems:function () {
                // summary:
                //		loads the items that are currently visible in the pane
                delete this._visibleLoadPending;
                var menu = this._menu;
                if (!menu) {
                    return;
                }
                var children = menu.getChildren();
                if (!children || !children.length) {
                    return;
                }
                var gpbme = function (n, m, pb) {
                    var s = dojo.getComputedStyle(n);
                    var r = 0;
                    if (m) {
                        r += dojo._getMarginExtents(n, s).t;
                    }
                    if (pb) {
                        r += dojo._getPadBorderExtents(n, s).t;
                    }
                    return r;
                };
                var topOffset = gpbme(this.domNode, false, true) +
                    gpbme(this.containerNode, true, true) +
                    gpbme(menu.domNode, true, true) +
                    gpbme(children[0].domNode, true, false);
                var h = dojo.contentBox(this.domNode).h;
                var minOffset = this.domNode.scrollTop - topOffset - (h / 2);
                var maxOffset = minOffset + (3 * h / 2);
                var menuItemsToLoad = dojo.filter(children, function (c) {
                    var cnt = c.domNode.offsetTop;
                    var s = c.store;
                    var i = c.item;
                    return (cnt >= minOffset && cnt <= maxOffset && !s.isItemLoaded(i));
                })
                var itemsToLoad = dojo.map(menuItemsToLoad, function (c) {
                    return c.item;
                });
                var onItems = dojo.hitch(this, function () {
                    var selectItem = this._getSelected();
                    var selectMenuItem;
                    dojo.forEach(itemsToLoad, function (item, idx) {
                        var newItem = this.parentWidget._getMenuItemForItem(item, this);
                        var oItem = menuItemsToLoad[idx];
                        var oIdx = oItem.getIndexInParent();
                        menu.removeChild(oItem);
                        if (newItem) {
                            if (selectItem && this.parentWidget._itemsMatch(newItem.item, selectItem.item)) {
                                selectMenuItem = newItem;
                            }
                            menu.addChild(newItem, oIdx);
                            if (menu.focusedChild == oItem) {
                                menu.focusChild(newItem);
                            }
                        }
                        oItem.destroy();
                    }, this);
                    this._checkScrollConnection(false);
                });
                this._doLoadItems(itemsToLoad, onItems);
            },

            _getSelected:function (/*dijit.Menu?*/ menu) {
                // summary:
                //		returns the selected menu item - or null if none are selected
                if (!menu) {
                    menu = this._menu;
                }
                if (menu) {
                    var children = this._menu.getChildren();
                    for (var i = 0, item; (item = children[i]); i++) {
                        if (dojo.hasClass(item.domNode, "dojoxRollingListItemSelected")) {
                            return item;
                        }
                    }
                }
                return null;
            },

            _setSelected:function (/*dijit.MenuItem?*/ item, /*dijit.Menu?*/ menu) {
                // summary:
                //		selects the given item in the given menu (defaults to pane's menu)
                if (!menu) {
                    menu = this._menu;
                }
                if (menu) {
                    dojo.forEach(menu.getChildren(), function (i) {
                        this.parentWidget._updateClass(i.domNode, "Item", {"Selected":(item && (i == item && !i.disabled))});
                    }, this);
                }
            }
        });

    dojo.declare("dojox.widget.RollingList",
        [dijit._Widget, dijit._Templated, dijit._Container], {
            // summary:
            //		a rolling list that can be tied to a data store with children

            // templateString: String
            //		The template to be used to construct the widget.
            templateString:dojo.cache("dojox.widget", "RollingList/RollingList.html", "<div class=\"dojoxRollingList ${className}\"\n\t><div class=\"dojoxRollingListContainer\" dojoAttachPoint=\"containerNode\" dojoAttachEvent=\"onkeypress:_onKey\"\n\t></div\n\t><div class=\"dojoxRollingListButtons\" dojoAttachPoint=\"buttonsNode\"\n        ><button dojoType=\"dijit.form.Button\" dojoAttachPoint=\"okButton\"\n\t\t\t\tdojoAttachEvent=\"onClick:_onExecute\">${okButtonLabel}</button\n        ><button dojoType=\"dijit.form.Button\" dojoAttachPoint=\"cancelButton\"\n\t\t\t\tdojoAttachEvent=\"onClick:_onCancel\">${cancelButtonLabel}</button\n\t></div\n></div>\n"),
            widgetsInTemplate:true,

            // className: string
            //		an additional class (or space-separated classes) to add for our widget
            className:"",

            // store: store
            //		the store we must use
            store:null,

            // query: object
            //		a query to pass to the datastore.  This is only used if items are null
            query:null,

            // queryOptions: object
            //		query options to be passed to the datastore
            queryOptions:null,

            // childrenAttrs: String[]
            //		one ore more attributes that holds children of a node
            childrenAttrs:["children"],

            // parentAttr: string
            //		the attribute to read for finding our parent item (if any)
            parentAttr:"",

            // value: item
            //		The value that has been selected
            value:null,

            // executeOnDblClick: boolean
            //		Set to true if you want to call onExecute when an item is
            //		double-clicked, false if you want to call onExecute yourself. (mainly
            //		used for popups to control how they want to be handled)
            executeOnDblClick:true,

            // preloadItems: boolean or int
            //		if set to true, then onItems will be called only *after* all items have
            //		been loaded (ie store.isLoaded will return true for all of them).  If
            //		false, then no preloading will occur.  If set to an integer, preloading
            //		will occur if the number of items is less than or equal to the value
            //		of the integer.  The onItems function will need to be aware of handling
            //		items that may not be loaded
            preloadItems:false,

            // showButtons: boolean
            //		if set to true, then buttons for "OK" and "Cancel" will be provided
            showButtons:false,

            // okButtonLabel: string
            //		The string to use for the OK button - will use dijit's common "OK" string
            //		if not set
            okButtonLabel:"",

            // cancelButtonLabel: string
            //		The string to use for the Cancel button - will use dijit's common
            //		"Cancel" string if not set
            cancelButtonLabel:"",

            // minPaneWidth: integer
            //		the minimum pane width (in px) for all child panes.  If they are narrower,
            //		the width will be increased to this value.
            minPaneWidth:0,

            postMixInProperties:function () {
                // summary:
                //		Mix in our labels, if they are not set
                this.inherited(arguments);
                var loc = dojo.i18n.getLocalization("dijit", "common");
                this.okButtonLabel = this.okButtonLabel || loc.buttonOk;
                this.cancelButtonLabel = this.cancelButtonLabel || loc.buttonCancel;
            },

            _setShowButtonsAttr:function (doShow) {
                // summary:
                //		Sets the visibility of the buttons for the widget
                var needsLayout = false;
                if ((this.showButtons != doShow && this._started) ||
                    (this.showButtons == doShow && !this.started)) {
                    needsLayout = true;
                }
                dojo.toggleClass(this.domNode, "dojoxRollingListButtonsHidden", !doShow);
                this.showButtons = doShow;
                if (needsLayout) {
                    if (this._started) {
                        this.layout();
                    } else {
                        window.setTimeout(dojo.hitch(this, "layout"), 0);
                    }
                }
            },

            _itemsMatch:function (/*item*/ item1, /*item*/ item2) {
                // summary:
                //		returns whether or not the two items match - checks ID if
                //		they aren't the exact same object
                if (!item1 && !item2) {
                    return true;
                } else if (!item1 || !item2) {
                    return false;
                }
                return (item1 == item2 ||
                    (this._isIdentity && this.store.getIdentity(item1) == this.store.getIdentity(item2)));
            },

            _removeAfter:function (/*Widget or int*/ idx) {
                // summary:
                //		removes all widgets after the given widget (or index)
                if (typeof idx != "number") {
                    idx = this.getIndexOfChild(idx);
                }
                if (idx >= 0) {
                    dojo.forEach(this.getChildren(), function (c, i) {
                        if (i > idx) {
                            this.removeChild(c);
                            c.destroyRecursive();
                        }
                    }, this);
                }
                var children = this.getChildren(), child = children[children.length - 1];
                var selItem = null;
                while (child && !selItem) {
                    var val = child._getSelected ? child._getSelected() : null;
                    if (val) {
                        selItem = val.item;
                    }
                    child = child.parentPane;
                }
                if (!this._setInProgress) {
                    this._setValue(selItem);
                }
            },

            addChild:function (/*dijit._Widget*/ widget, /*int?*/ insertIndex) {
                // summary:
                //		adds a child to this rolling list - if passed an insertIndex,
                //		then all children from that index on will be removed and destroyed
                //		before adding the child.
                if (insertIndex > 0) {
                    this._removeAfter(insertIndex - 1);
                }
                this.inherited(arguments);
                if (!widget._started) {
                    widget.startup();
                }
                widget.attr("minWidth", this.minPaneWidth);
                this.layout();
                if (!this._savedFocus) {
                    widget.focus();
                }
            },

            _setMinPaneWidthAttr:function (value) {
                // summary:
                //		Sets the min pane width of all children
                if (value !== this.minPaneWidth) {
                    this.minPaneWidth = value;
                    dojo.forEach(this.getChildren(), function (c) {
                        c.attr("minWidth", value);
                    });
                }
            },

            _updateClass:function (/* Node */ node, /* String */ type, /* Object? */ options) {
                // summary:
                //		sets the state of the given node with the given type and options
                // options:
                //		an object with key-value-pairs.  The values are boolean, if true,
                //		the key is added as a class, if false, it is removed.
                if (!this._declaredClasses) {
                    this._declaredClasses = ("dojoxRollingList " + this.className).split(" ");
                }
                dojo.forEach(this._declaredClasses, function (c) {
                    if (c) {
                        dojo.addClass(node, c + type);
                        for (var k in options || {}) {
                            dojo.toggleClass(node, c + type + k, options[k]);
                        }
                        dojo.toggleClass(node, c + type + "FocusSelected",
                            (dojo.hasClass(node, c + type + "Focus") && dojo.hasClass(node, c + type + "Selected")));
                        dojo.toggleClass(node, c + type + "HoverSelected",
                            (dojo.hasClass(node, c + type + "Hover") && dojo.hasClass(node, c + type + "Selected")));
                    }
                });
            },

            scrollIntoView:function (/*dijit._Widget*/ childWidget) {
                // summary:
                //		scrolls the given widget into view
                if (this._scrollingTimeout) {
                    window.clearTimeout(this._scrollingTimeout);
                }
                delete this._scrollingTimeout;
                this._scrollingTimeout = window.setTimeout(dojo.hitch(this, function () {
                    if (childWidget.domNode) {
                        dojo.window.scrollIntoView(childWidget.domNode);
                    }
                    delete this._scrollingTimeout;
                    return;
                }), 1);
            },

            resize:function (args) {
                dijit.layout._LayoutWidget.prototype.resize.call(this, args);
            },

            layout:function () {
                var children = this.getChildren();
                if (this._contentBox) {
                    var bn = this.buttonsNode;
                    var height = this._contentBox.h - dojo.marginBox(bn).h -
                        dojox.html.metrics.getScrollbar().h;
                    dojo.forEach(children, function (c) {
                        dojo.marginBox(c.domNode, {h:height});
                    });
                }
                if (this._focusedPane) {
                    var foc = this._focusedPane;
                    delete this._focusedPane;
                    if (!this._savedFocus) {
                        foc.focus();
                    }
                } else if (children && children.length) {
                    if (!this._savedFocus) {
                        children[0].focus();
                    }
                }
            },

            _onChange:function (/*item*/ value) {
                this.onChange(value);
            },

            _setValue:function (/* item */ value) {
                // summary:
                //		internally sets the value and fires onchange
                delete this._setInProgress;
                if (!this._itemsMatch(this.value, value)) {
                    this.value = value;
                    this._onChange(value);
                }
            },

            _setValueAttr:function (/* item */ value) {
                // summary:
                //		sets the value of this widget to the given store item
                if (this._itemsMatch(this.value, value) && !value) {
                    return;
                }
                if (this._setInProgress && this._setInProgress === value) {
                    return;
                }
                this._setInProgress = value;
                if (!value || !this.store.isItem(value)) {
                    var pane = this.getChildren()[0];
                    pane._setSelected(null);
                    this._onItemClick(null, pane, null, null);
                    return;
                }

                var fetchParentItems = dojo.hitch(this, function (/*item*/ item, /*function*/callback) {
                    // summary:
                    //		Fetches the parent items for the given item
                    var store = this.store, id;
                    if (this.parentAttr && store.getFeatures()["dojo.data.api.Identity"] &&
                        ((id = this.store.getValue(item, this.parentAttr)) || id === "")) {
                        // Fetch by parent attribute
                        var cb = function (i) {
                            if (store.getIdentity(i) == store.getIdentity(item)) {
                                callback(null);
                            } else {
                                callback([i]);
                            }
                        };
                        if (id === "") {
                            callback(null);
                        } else if (typeof id == "string") {
                            store.fetchItemByIdentity({identity:id, onItem:cb});
                        } else if (store.isItem(id)) {
                            cb(id);
                        }
                    } else {
                        // Fetch by finding children
                        var numCheck = this.childrenAttrs.length;
                        var parents = [];
                        dojo.forEach(this.childrenAttrs, function (attr) {
                            var q = {};
                            q[attr] = item;
                            store.fetch({query:q, scope:this,
                                onComplete:function (items) {
                                    if (this._setInProgress !== value) {
                                        return;
                                    }
                                    parents = parents.concat(items);
                                    numCheck--;
                                    if (numCheck === 0) {
                                        callback(parents);
                                    }
                                }
                            });
                        }, this);
                    }
                });

                var setFromChain = dojo.hitch(this, function (/*item[]*/itemChain, /*integer*/idx) {
                    // summary:
                    //		Sets the value of the widget at the given index in the chain - onchanges are not
                    //		fired here
                    var set = itemChain[idx];
                    var child = this.getChildren()[idx];
                    var conn;
                    if (set && child) {
                        var fx = dojo.hitch(this, function () {
                            if (conn) {
                                this.disconnect(conn);
                            }
                            delete conn;
                            if (this._setInProgress !== value) {
                                return;
                            }
                            var selOpt = dojo.filter(child._menu.getChildren(), function (i) {
                                return this._itemsMatch(i.item, set);
                            }, this)[0];
                            if (selOpt) {
                                idx++;
                                child._menu.onItemClick(selOpt, {type:"internal",
                                    stopPropagation:function () {
                                    },
                                    preventDefault:function () {
                                    }});
                                if (itemChain[idx]) {
                                    setFromChain(itemChain, idx);
                                } else {
                                    this._setValue(set);
                                    this.onItemClick(set, child, this.getChildItems(set));
                                }
                            }
                        });
                        if (!child.isLoaded) {
                            conn = this.connect(child, "onLoad", fx);
                        } else {
                            fx();
                        }
                    } else if (idx === 0) {
                        this.set("value", null);
                    }
                });

                var parentChain = [];
                var onParents = dojo.hitch(this, function (/*item[]*/ parents) {
                    // summary:
                    //		recursively grabs the parents - only the first one is followed
                    if (parents && parents.length) {
                        parentChain.push(parents[0]);
                        fetchParentItems(parents[0], onParents);
                    } else {
                        if (!parents) {
                            parentChain.pop();
                        }
                        parentChain.reverse();
                        setFromChain(parentChain, 0);
                    }
                });

                // Only set the value in display if we are shown - if we are in a dropdown,
                // and are hidden, don't actually do the scrolling in the display (it can
                // mess up layouts)
                var ns = this.domNode.style;
                if (ns.display == "none" || ns.visibility == "hidden") {
                    this._setValue(value);
                } else if (!this._itemsMatch(value, this._visibleItem)) {
                    onParents([value]);
                }
            },

            _onItemClick:function (/* Event */ evt, /* dijit._Contained */ pane, /* item */ item, /* item[]? */ children) {
                // summary:
                //		internally called when a widget should pop up its child

                if (evt) {
                    var itemPane = this._getPaneForItem(item, pane, children);
                    var alreadySelected = (evt.type == "click" && evt.alreadySelected);

                    if (alreadySelected && itemPane) {
                        this._removeAfter(pane.getIndexInParent() + 1);
                        var next = pane.getNextSibling();
                        if (next && next._setSelected) {
                            next._setSelected(null);
                        }
                        this.scrollIntoView(next);
                    } else if (itemPane) {
                        this.addChild(itemPane, pane.getIndexInParent() + 1);
                        if (this._savedFocus) {
                            itemPane.focus(true);
                        }
                    } else {
                        this._removeAfter(pane);
                        this.scrollIntoView(pane);
                    }
                } else if (pane) {
                    this._removeAfter(pane);
                    this.scrollIntoView(pane);
                }
                if (!evt || evt.type != "internal") {
                    this._setValue(item);
                    this.onItemClick(item, pane, children);
                }
                this._visibleItem = item;
            },

            _getPaneForItem:function (/* item? */ item, /* dijit._Contained? */ parentPane, /* item[]? */ children) {
                // summary:
                //		gets the pane for the given item, and mixes in our needed parts
                //		Returns the pane for the given item (null if the root pane) - after mixing in
                //		its stuff.
                var ret = this.getPaneForItem(item, parentPane, children);
                ret.store = this.store;
                ret.parentWidget = this;
                ret.parentPane = parentPane || null;
                if (!item) {
                    ret.query = this.query;
                    ret.queryOptions = this.queryOptions;
                } else if (children) {
                    ret.items = children;
                } else {
                    ret.items = [item];
                }
                return ret;
            },

            _getMenuItemForItem:function (/*item*/ item, /* dijit._Contained */ parentPane) {
                // summary:
                //		returns a widget for the given store item.  The returned
                //		item will be added to this widget's container widget.  null will
                //		be passed in for an "empty" item.
                var store = this.store;
                if (!item || !store || !store.isItem(item)) {
                    var i = new dijit.MenuItem({
                        label:"---",
                        disabled:true,
                        iconClass:"dojoxEmpty",
                        focus:function () {
                            // Do nothing on focus of this guy...
                        }
                    });
                    this._updateClass(i.domNode, "Item");
                    return i;
                } else {
                    var itemLoaded = store.isItemLoaded(item);
                    var childItems = itemLoaded ? this.getChildItems(item) : undefined;
                    var widgetItem;
                    if (childItems) {
                        widgetItem = this.getMenuItemForItem(item, parentPane, childItems);
                        widgetItem.children = childItems;
                        this._updateClass(widgetItem.domNode, "Item", {"Expanding":true});
                        if (!widgetItem._started) {
                            var c = widgetItem.connect(widgetItem, "startup", function () {
                                this.disconnect(c);
                                dojo.style(this.arrowWrapper, "visibility", "");
                            });
                        } else {
                            dojo.style(widgetItem.arrowWrapper, "visibility", "");
                        }
                    } else {
                        widgetItem = this.getMenuItemForItem(item, parentPane, null);
                        if (itemLoaded) {
                            this._updateClass(widgetItem.domNode, "Item", {"Single":true});
                        } else {
                            this._updateClass(widgetItem.domNode, "Item", {"Unloaded":true});
                            widgetItem.attr("disabled", true);
                        }
                    }
                    widgetItem.store = this.store;
                    widgetItem.item = item;
                    if (!widgetItem.label) {
                        widgetItem.attr("label", this.store.getLabel(item).replace(/</, "&lt;"));
                    }
                    if (widgetItem.focusNode) {
                        var self = this;
                        widgetItem.focus = function () {
                            // Don't set our class
                            if (!this.disabled) {
                                try {
                                    this.focusNode.focus();
                                } catch (e) {
                                }
                            }
                        };
                        widgetItem.connect(widgetItem.focusNode, "onmouseenter", function () {
                            if (!this.disabled) {
                                self._updateClass(this.domNode, "Item", {"Hover":true});
                            }
                        });
                        widgetItem.connect(widgetItem.focusNode, "onmouseleave", function () {
                            if (!this.disabled) {
                                self._updateClass(this.domNode, "Item", {"Hover":false});
                            }
                        });
                        widgetItem.connect(widgetItem.focusNode, "blur", function () {
                            self._updateClass(this.domNode, "Item", {"Focus":false, "Hover":false});
                        });
                        widgetItem.connect(widgetItem.focusNode, "focus", function () {
                            self._updateClass(this.domNode, "Item", {"Focus":true});
                            self._focusedPane = parentPane;
                        });
                        if (this.executeOnDblClick) {
                            widgetItem.connect(widgetItem.focusNode, "ondblclick", function () {
                                self._onExecute();
                            });
                        }
                    }
                    return widgetItem;
                }
            },

            _setStore:function (/* dojo/data/api/Read */ store) {
                // summary:
                //		sets the store for this widget */
                if (store === this.store && this._started) {
                    return;
                }
                this.store = store;
                this._isIdentity = store.getFeatures()["dojo.data.api.Identity"];
                var rootPane = this._getPaneForItem();
                this.addChild(rootPane, 0);
            },

            _onKey:function (/*Event*/ e) {
                // summary:
                //		called when a keypress event happens on this widget
                if (e.charOrCode == dojo.keys.BACKSPACE) {
                    dojo.stopEvent(e);
                    return;
                } else if (e.charOrCode == dojo.keys.ESCAPE && this._savedFocus) {
                    try {
                        dijit.focus(this._savedFocus);
                    } catch (e) {
                    }
                    dojo.stopEvent(e);
                    return;
                } else if (e.charOrCode == dojo.keys.LEFT_ARROW ||
                    e.charOrCode == dojo.keys.RIGHT_ARROW) {
                    dojo.stopEvent(e);
                    return;
                }
            },

            _resetValue:function () {
                // summary:
                //		function called when the value is reset.
                this.set("value", this._lastExecutedValue);
            },

            _onCancel:function () {
                // summary:
                //		function called when the cancel button is clicked.  It
                //		resets its value to whatever was last executed and then cancels
                this._resetValue();
                this.onCancel();
            },

            _onExecute:function () {
                // summary:
                //		function called when the OK button is clicked or when an
                //		item is selected (double-clicked or "enter" pressed on it)
                this._lastExecutedValue = this.get("value");
                this.onExecute();
            },

            focus:function () {
                // summary:
                //		sets the focus state of this widget
                var wasSaved = this._savedFocus;
                this._savedFocus = dijit.getFocus(this);
                if (!this._savedFocus.node) {
                    delete this._savedFocus;
                }
                if (!this._focusedPane) {
                    var child = this.getChildren()[0];
                    if (child && !wasSaved) {
                        child.focus(true);
                    }
                } else {
                    this._savedFocus = dijit.getFocus(this);
                    var foc = this._focusedPane;
                    delete this._focusedPane;
                    if (!wasSaved) {
                        foc.focus(true);
                    }
                }
            },

            handleKey:function (/*Event*/e) {
                // summary:
                //		handle the key for the given event - called by dropdown
                //		widgets
                if (e.keyCode == dojo.keys.DOWN_ARROW) {
                    delete this._savedFocus;
                    this.focus();
                    return false;
                } else if (e.keyCode == dojo.keys.ESCAPE) {
                    this._onCancel();
                    return false;
                }
                return true;
            },

            _updateChildClasses:function () {
                // summary:
                //		Called when a child is added or removed - so that we can
                //		update the classes for styling the "current" one differently than
                //		the others
                var children = this.getChildren();
                var length = children.length;
                dojo.forEach(children, function (c, idx) {
                    dojo.toggleClass(c.domNode, "dojoxRollingListPaneCurrentChild", (idx == (length - 1)));
                    dojo.toggleClass(c.domNode, "dojoxRollingListPaneCurrentSelected", (idx == (length - 2)));
                });
            },

            startup:function () {
                if (this._started) {
                    return;
                }
                if (!this.getParent || !this.getParent()) {
                    this.resize();
                    this.connect(dojo.global, "onresize", "resize");
                }
                this.connect(this, "addChild", "_updateChildClasses");
                this.connect(this, "removeChild", "_updateChildClasses");
                this._setStore(this.store);
                this.set("showButtons", this.showButtons);
                this.inherited(arguments);
                this._lastExecutedValue = this.get("value");
            },

            getChildItems:function (/*item*/ item) {
                // summary:
                //		Returns the child items for the given store item
                var childItems, store = this.store;
                dojo.forEach(this.childrenAttrs, function (attr) {
                    var vals = store.getValues(item, attr);
                    if (vals && vals.length) {
                        childItems = (childItems || []).concat(vals);
                    }
                });
                return childItems;
            },

            getMenuItemForItem:function (/*item*/ item, /* dijit._Contained */ parentPane, /* item[]? */ children) {
                // summary:
                //		user overridable function to return a widget for the given item
                //		and its children.
                return new dijit.MenuItem({});
            },

            getPaneForItem:function (/* item? */ item, /* dijit._Contained? */ parentPane, /* item[]? */ children) {
                // summary:
                //		user-overridable function to return a pane that corresponds
                //		to the given item in the store.  It can return null to not add a new pane
                //		(ie, you are planning on doing something else with it in onItemClick)

                // Item is undefined for the root pane, children is undefined for non-group panes
                if (!item || children) {
                    return new dojox.widget._RollingListGroupPane({});
                } else {
                    return null;
                }
            },

            onItemClick:function (/* item */ item, /* dijit._Contained */ pane, /* item[]? */ children) {
                // summary:
                //		called when an item is clicked - it receives the store item
            },

            onExecute:function () {
                // summary:
                //		exists so that popups don't disappear too soon
            },

            onCancel:function () {
                // summary:
                //		exists so that we can close ourselves if we wish
            },

            onChange:function (/* item */ value) {
                // summary:
                //		called when the value of this widget has changed
            }

        });

});
